/* Copyright (c) CERN (except method fwt)
 * * Artistic License 2.0
 * */

import java.util.Random;
import java.lang.Math;
import java.util.Arrays;

/**
 * Opsogram based pseudo distribution
 *
 * @author Xavier Grehant
 */
public class Opso extends DistroUtils implements Distro {

/**
 * Parameters of the distribution: number of occurences in bins
 */
   int[] params;

/**
 * A uniform random number generator.
 */
   Random rand;

/**
 * Constructor taking a sample set and the number of params.
 * @param array the sample set.
 * @param paramn number of parameters of the pseudo-distribution.
 */
   public Opso(int[] array, int paramn) {
      rand = new Random();
      Arrays.sort(array);
      this.params = dispatch(array, paramn + 1);
   }

/**
 * Dispatching data in the histogram
 * @return the first parameters of the DCT.
 * @param array the samples.
 * @param paramn the number of parameters in the output.
 * @return a histogram of increasing values.
 */
   int[] dispatch(int[] array, int paramn) {
      float step = (float) array.length / (float) (paramn - 1);
      int[] output = new int[paramn];
      output[0] = array[0];
      output[paramn - 1] = array[array.length - 1];
      for (int i = 1; i < paramn - 1; i++)
         output[i] = array[(int) (step * (float) i)];
      return output;
   }

/**
 * inverse cdf
 * @return the value of the modeled function whose cdf corresponds to the specified probability.
 * @param value at the specified probability.
 */
   double icdf(double value) {
      if (value == 1.) return params[params.length - 1];
      double left = params[(int) (value * (double) (params.length - 1))];
      double right = params[(int) (value * (double) (params.length - 1)) + 1];
      double extra = value * (double) params.length - (double) (int) (value * (double) params.length);
      return left + extra * (right - left);
   }

/**
 * Random number generator according to the pseudo-distribution.
 * @return random number according to the pseudo-distribution.
 */
   public double nextDouble() {
      return icdf(rand.nextDouble());
   }

/**
 * Cumulative density function of the pseudo-distribution.
 * @return P(X <= x)
 * @param value the point at which the cdf is calculated.
 */
   public double cdf(double value) {
      if (value >= params[params.length - 1]) return 1.;
      int i = 0;
      for (i = 0; i < params.length - 1 && params[i + 1] < (int) value; i++);
      double left = (double) i / (double) (params.length - 1);
      double right = (double) (i + 1) / (double) (params.length - 1);
      double extra = value - params[i];
      return left + extra / (double) (params[i + 1] - params[i]);
   }
}
